﻿using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Newtonsoft.Json;

namespace Ecobee.Net
{
	public abstract class ApiObject : IApiObject
	{
		public event PropertyChangedEventHandler PropertyChanged = null;

		/// <summary>
		/// Returns a key that uniquely identifies this instance.
		/// </summary>
		[JsonIgnore()]
		public virtual string UniqueKey
		{
			get
			{
				return this.OnUniqueKey();
			}
		}

		/// <summary>
		/// Extracts and returns the unique key from the 
		/// underlying JSON object.
		/// </summary>
		/// <param name="dataObject">The deserialized JSON object.</param>
		/// <returns>A System.String key that uniquely identifies this instance.</returns>
		public virtual string GetKeyValue(dynamic dataObject)
		{
			return this.OnGetKeyValue(dataObject);
		}

		/// <summary>
		/// Convert this object instance to JSON.
		/// </summary>
		/// <returns>A System.String value representing the JSON equivalent of this object.</returns>
		public virtual string Serialize()
		{
			return this.OnSerialize();
		}

		/// <summary>
		/// Convert this object instance from JSON.
		/// </summary>
		/// <param name="json">A System.String value representing the JSON equivalent of this object.</param>
		public virtual void Deserialize(string json)
		{
			this.OnDeserialize(json);
		}

		/// <summary>
		/// Load properties from a JSON object.
		/// </summary>
		/// <param name="dataObject">A JSON representation of this object.</param>
		public virtual void FromDataObject(dynamic dataObject)
		{
			if (dataObject != null)
			{
				this.OnFromDataObject(dataObject);
			}
		}

		/// <summary>
		/// Converts this instance into a JSON object.
		/// </summary>
		/// <returns></returns>
		public virtual dynamic JsonObject()
		{
			return JsonConvert.DeserializeObject(this.Serialize());
		}

		protected virtual string OnSerialize()
		{
			// ***
			// *** Do a default serialization. In most cases if this
			// *** object is used by the API this will not work because
			// *** the property names do not match.
			// *** 
			return JsonConvert.SerializeObject(this);
		}

		protected virtual void OnDeserialize(string json)
		{
			dynamic dataObject = Newtonsoft.Json.JsonConvert.DeserializeObject(json);
			this.OnFromDataObject(dataObject);
		}

		protected virtual void OnFromDataObject(dynamic dataObject)
		{
			throw new NotImplementedException();
		}

		protected virtual void LinkObject(IApiObject apiObject)
		{
			apiObject.PropertyChanged += apiObject_PropertyChanged;
		}

		private void apiObject_PropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			if (this.PropertyChanged != null)
			{
				this.PropertyChanged(sender, new PropertyChangedEventArgs(e.PropertyName));
			}
		}

		/// <summary>
		/// Checks if a property already matches a desired value.  Sets the property and
		/// notifies listeners only when necessary.
		/// </summary>
		/// <typeparam name="T">Type of the property.</typeparam>
		/// <param name="storage">Reference to a property with both getter and setter.</param>
		/// <param name="value">Desired value for the property.</param>
		/// <param name="propertyName">Name of the property used to notify listeners.  This
		/// value is optional and can be provided automatically when invoked from compilers that
		/// support CallerMemberName.</param>
		/// <returns>True if the value was changed, false if the existing value matched the
		/// desired value.</returns>
		protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
		{
			bool returnValue = false;

			if (value is IApiObject)
			{
				this.LinkObject((IApiObject)value);
			}
			else if (value is IApiObjectCollection)
			{
				((IApiObjectCollection)value).ApiObjectCollectionChanged += ApiObject_CollectionChanged;
			}

			if (object.Equals(storage, value))
			{
				returnValue = false;
			}
			else
			{
				storage = value;
				this.OnPropertyChanged(propertyName);
				returnValue = true;
			}

			return returnValue;
		}

		private void ApiObject_CollectionChanged(object sender, ApiObjectCollectionChangedEventArgs e)
		{
			this.OnPropertyChanged(e.PropertyName);
		}

		/// <summary>
		/// Notifies listeners that a property value has changed.
		/// </summary>
		/// <param name="propertyName">Name of the property used to notify listeners.  This
		/// value is optional and can be provided automatically when invoked from compilers
		/// that support <see cref="CallerMemberNameAttribute"/>.</param>
		protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
		{
			if (this.PropertyChanged != null)
			{
				this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
			}
		}

		protected virtual string OnUniqueKey()
		{
			return this.GetHashCode().ToString();
		}

		protected virtual string OnGetKeyValue(dynamic dataObject)
		{
			return string.Empty;
		}
	}
}
